using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Recipes;

internal class AsyncSemaphore
{
    private static readonly Task _completed = Task.FromResult(true);
    private readonly Queue<TaskCompletionSource<bool>> _waiters = new();
    private int m_currentCount;

    public AsyncSemaphore(int initialCount)
    {
        if (initialCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(initialCount));
        }

        m_currentCount = initialCount;
    }

    public Task WaitAsync()
    {
        lock (_waiters)
        {
            if (m_currentCount > 0)
            {
                --m_currentCount;
                return _completed;
            }
            else
            {
                var waiter = new TaskCompletionSource<bool>();
                _waiters.Enqueue(waiter);
                return waiter.Task;
            }
        }
    }

    public void Release()
    {
        TaskCompletionSource<bool> toRelease = null;

        lock (_waiters)
        {
            if (_waiters.Count > 0)
            {
                toRelease = _waiters.Dequeue();
            }
            else
            {
                ++m_currentCount;
            }
        }

        toRelease?.SetResult(true);
    }
}